using System;
using System.CodeDom;
using System.CodeDom.Compiler;

using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

using Weazel.Gezel.Model;

namespace Weazel.Gezel.Conversions.Model
{
  public class DisplayType : Type
  {
    private ControllerType controllerType;
    private DirectiveExpression directive;

    private Method evaluateMethod;
    private Method initializeMethod;

    public DisplayType(DirectiveExpression directive, SignalFlowGraph sfg, ControllerType controllerType, SoftwareModel softwareModel)
      : base(String.Format("Display_{0}", directive.Name.Replace(".", "_")), softwareModel)
    {
      Debug.Assert(directive.ExpressionType == ExpressionType.DisplayDirective);

      this.controllerType = controllerType;
      this.directive = directive;

      buildEvaluateMethod(sfg, controllerType);

      buildInitializeMethod();
    }

    private void buildInitializeMethod()
    {
      initializeMethod = AddMethod("Initialize");
      // initialize integer fields
      foreach (Field field in this.Fields.Values)
      {
        IntegerField integerField = field as IntegerField;
        if (integerField != null)
        {
          if (integerField.InitialValue != "0")
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.InitialValue),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
          else
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
        }
      }
    }

   

    private void buildEvaluateMethod(SignalFlowGraph sfg, ControllerType controllerType)
    {
      evaluateMethod = AddMethod("Evaluate");
      int[] controlSteps = controllerType.GetControlStepsForSfg(sfg);

      CodeSnippetStatement switchStatement = null;
      switch (softwareModel.Language)
      {
        case TargetLanguage.CSharp:
          switchStatement = new CodeSnippetStatement(string.Format("      switch(Simulator.controller_{0}.CurrentControlStep)", controllerType.Controller.Name.Replace(".", "_")));
          break;
        case TargetLanguage.VisualC:
        case TargetLanguage.IsoCpp:
          switchStatement = new CodeSnippetStatement(string.Format("      switch(Simulator::controller_{0}->CurrentControlStep)", controllerType.Controller.Name.Replace(".", "_")));
          break;
        case TargetLanguage.VisualBasic:
        case TargetLanguage.JSharp:
        default:
          throw new ApplicationException("Unsupported language");
      }

      System.Diagnostics.Debug.Assert(switchStatement != null);
      evaluateMethod.Statements.Add(switchStatement);
      evaluateMethod.Statements.Add(new CodeSnippetStatement("      {"));

      // add cases for all controlsteps which invokes display directive
      foreach (int controlStep in controlSteps)
      {
        evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("      case {0}:", controlStep)));
      }

      // build logic to print to screen

      if (softwareModel.Language == TargetLanguage.IsoCpp)
      {
        int radix = 16;
        foreach (Expression arg in directive.Arguments)
        {
          switch (arg.ExpressionType)
          {
            case ExpressionType.Constant:
              // as a small optimization, this is handled as a special case instead of the default
              // such that we avoids generating an Integer member variable for the constant etc. 
              string str = (arg as LeafExpression).IntegerValue.ToString(radix);
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"{0}\");", str)));
              break;
            case ExpressionType.Str:
              string str2 = (arg as LeafExpression).Value;
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"{0}\");", str2)));
              break;
            case ExpressionType.DatapathDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"{0}\");", sfg.Parent.Name)));
              break;
            case ExpressionType.CycleDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement("        printf(\"%i\", Simulator::CurrentCycle);"));
              break;
            case ExpressionType.SfgDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"{0}\");", sfg.Name)));
              break;
            case ExpressionType.HexDirective:
              radix = 16;
              break;
            case ExpressionType.BinDirective:
              radix = 2;
              break;
            case ExpressionType.DecDirective:
              radix = 10;
              break;
            default:
              // add code to evaluate expression to display
              CodeFieldReferenceExpression generatedIdentifier =
                  ExpressionConversionHelper.BuildEvaluateMethodRecursive(arg, evaluateMethod.Statements, this);

              // TODO: below is a unsafe hack
              
              string fieldName = null;

              if (generatedIdentifier.TargetObject is CodeFieldReferenceExpression)
              {
                fieldName = string.Format(
                "Simulator::{0}->{1}",
                (generatedIdentifier.TargetObject as CodeFieldReferenceExpression).FieldName,
                generatedIdentifier.FieldName);
              }
              else if (generatedIdentifier.TargetObject is CodeThisReferenceExpression)
              {
                fieldName = string.Format("this->{0}", generatedIdentifier.FieldName);
              }
              
              if (radix == 2)
              {
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        {0}->ToBinaryString(DisplayBuffer, BUFFER_SIZE);", fieldName)));
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"%s\", DisplayBuffer);", fieldName)));
              }
              else if (radix == 10)
              {
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        {0}->ToDecimalString(DisplayBuffer, BUFFER_SIZE);", fieldName)));
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"%s\", DisplayBuffer);", fieldName)));
              }
              else if (radix == 16)
              {
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        {0}->ToHexadecimalString(DisplayBuffer, BUFFER_SIZE);", fieldName)));
                evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        printf(\"%s\", DisplayBuffer);", fieldName)));
              }
              else // check for invalid radixes
              {
                Debug.Assert(false);
              }
              break;
          }
        }
        evaluateMethod.Statements.Add(new CodeSnippetStatement("        printf(\"\\n\");"));
      }
      else
      {
        Debug.Assert(softwareModel.Language == TargetLanguage.CSharp);
        int radix = 16;
        foreach (Expression arg in directive.Arguments)
        {
          switch (arg.ExpressionType)
          {
            case ExpressionType.Constant:
              // as a small optimization, this is handled as a special case instead of the default
              // such that we avoids generating an Integer member variable for the constant etc. 
              string str = (arg as LeafExpression).IntegerValue.ToString(radix);
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        Console.Write(\"{0}\");", str)));
              break;
            case ExpressionType.Str:
              string str2 = (arg as LeafExpression).Value;
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        Console.Write(\"{0}\");", str2)));
              break;
            case ExpressionType.DatapathDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        Console.Write(\"{0}\");", sfg.Parent.Name)));
              break;
            case ExpressionType.CycleDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement("        Console.Write(Simulator.CurrentCycle);"));
              break;
            case ExpressionType.SfgDirective:
              evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("        Console.Write(\"{0}\");", sfg.Name)));
              break;
            case ExpressionType.HexDirective:
              radix = 16;
              break;
            case ExpressionType.BinDirective:
              radix = 2;
              break;
            case ExpressionType.DecDirective:
              radix = 10;
              break;
            default:
              // add code to evaluate expression to display
              CodeFieldReferenceExpression generatedIdentifier =
                  ExpressionConversionHelper.BuildEvaluateMethodRecursive(arg, evaluateMethod.Statements, this);

              if (radix == 2)
              {
                CodeMethodInvokeExpression toBinaryStringMethod =
                  new CodeMethodInvokeExpression(generatedIdentifier, "ToBinaryString");

                CodeMethodInvokeExpression writeLineMethod =
                  new CodeMethodInvokeExpression(
                    new CodeTypeReferenceExpression("Console"),
                    "Write",
                    toBinaryStringMethod
                  );
                evaluateMethod.Statements.Add(writeLineMethod);
              }
              else if (radix == 10)
              {
                CodeMethodInvokeExpression toDecimalStringMethod =
                  new CodeMethodInvokeExpression(generatedIdentifier, "ToDecimalString");

                CodeMethodInvokeExpression writeLineMethod =
                  new CodeMethodInvokeExpression(
                    new CodeTypeReferenceExpression("Console"),
                    "Write",
                    toDecimalStringMethod
                  );
                evaluateMethod.Statements.Add(writeLineMethod);
              }
              else if (radix == 16)
              {
                CodeMethodInvokeExpression toHexadecimalStringMethod =
                  new CodeMethodInvokeExpression(generatedIdentifier, "ToHexadecimalString");

                CodeMethodInvokeExpression writeLineMethod =
                  new CodeMethodInvokeExpression(
                    new CodeTypeReferenceExpression("Console"),
                    "Write",
                    toHexadecimalStringMethod
                  );
                evaluateMethod.Statements.Add(writeLineMethod);
              }
              else // check for invalid radixes
              {
                Debug.Assert(false);
              }
              break;
          }
        }
        evaluateMethod.Statements.Add(new CodeSnippetStatement("        Console.WriteLine();"));
      }

      evaluateMethod.Statements.Add(new CodeSnippetStatement("        break;"));
      evaluateMethod.Statements.Add(new CodeSnippetStatement("      }"));
    }
  }
}
